Understanding the Common Information Model

By Entropy/Farbrausch

This article is the second part of the WMI tutorial series for DevX. It focuses on the Common Information Model Version 2 (CIMV2) and its integration with the Microsoft Windows Operating System. CIMV2 is the base technology for managing distributed corporate computer systems using Web Based Enterprise Management (WBEM), which is developed and maintained by the DMTF (Distributed Management Task Force). Windows Management Instrumentation (WMI) is the Windows implementation of WBEM.

CIMV2 represents the schema structure that describes all objects of a managed system. CIMV2 is a very complex system with many ways to express any given object in the WBEM repository; therefore, a complete explanation of CIMV2 is beyond the scope of this article. Instead, I'll focus on elements you need understand the inner structure of WMI.

System Requirements

To get real benefit from this article, you should have a good understanding of C# and of object-oriented programming in general. A good knowledge of the Windows environment, including Services, the Event Log and Active Directory would be very helpful too. There is no real need for a deep understanding of system internals, but an understanding of basics is advantageous.

WMI is available in Win ME, Windows 2000, or newer Windows versions. The WMI SDK includes a WMI Provider for Windows NT 4.0 SP 3. There are also versions for Windows 9x (although these have some limitations).

The accompanying examples are meant to be used with the final release of Visual Studio.NET (2002) or newer on Windows 2000 or XP machines. For older versions of the windows operating system there are WMI Providers in the WMI SDK, which you can find in the download section of http://msdn.microsoft.com.

The Windows Management Instrumentation Extensions for Microsoft Visual Studio .NET are helpful tools for .NET developers and can help you speed up WMI application development.

Why Would You Need CIM?

Imagine a typical enterprise application — not just the software, also the hardware and user environment. You have the network, various system devices and storage devices, an operating system, a database, and finally, the application. On top of all these you also typically have user policies, a management infrastructure and the security system. To manage all these components in one place you'll need some kind of flexible repository with schema definitions for the different kinds of components. That's the basic idea behind the Common Information Model.

CIM is an object-oriented design schema capable of representing a wide range of objects. It uses OOP features such as abstraction, inheritance, dependencies, aggregation and scoping to provide an extensible base schema that's able to represent information about all the objects mentioned above.

A CIM Schema:

- defines classes that can appear in a namespace
- defines properties and methods that can appear in a class
- defines which classes are child classes
- defines the order of classes
- defines the number of classes
- defines whether a class is abstract or can provide implementation
- defines data types for properties and methods
- defines default and fixed values for properties and methods

Andrea Westerinen, the DMTF VP of technology and work group chairs at the DMTF 2002 Developers' Conference defines an Information Model as:

"An abstraction and representation of the entities in a managed environment – their properties, operations and relationships. It is independent of any specific repository, application, protocol or platform."

The Core Information Model is the basic schema behind the CIM and contains information applicable to all management domains. The Common Information Model addresses specific domains like system devices, applications, networks, users and many others. It is derived from the Core Model and subclasses its schemas to extend the functionality of the base classes. Since the Common Information Model is designed to be extended by various vendors its contained models can overlap or have cross-references.

CIM's Object-oriented Approach

The goal behind each well done object oriented design is to model the world in terms of objects: An object is an abstraction, consisting of a set of related data and behaviors. E.g.: A car can have properties such as color, manufacturer, seats, and EngineType, and methods such as accelerate() and brake(). The system treats each object as a named entity that has a set of characteristics (properties and methods), behavior and a unique identity.

The CIM schema defines the classes that form the blueprint for these objects. But it also describes relationships between those objects. Inheritance hierarchies refine and specialize the attributes and behavior of a group of objects; association hierarchies relate objects to each other. For example: "The administrator is an instance of a user account class and is associated with the group he belongs to and also with his custom desktop settings."

The CIM models associations with special classes that represent the relationship between two or more objects. Therefore they can have properties, methods, and events like any other class. They also inherit semantics, properties and behavior from superclasses. Aggregation associations are distinguished by the "Aggregation" or "Aggregate" keyword. They represent whole-part relationships and members-of collections.

These five axioms summarize all the above information about the object-oriented approach:

- Instances are things.
- Properties are attributes.
- Relationships are pairs of attributes.
- Classes are types of things.
- Subclasses are subtypes of things.

The Core Model

Figure 1 shows two of the most important CIM classes. CIM_PhysicalElement and CIM_LogicalElement. Both are derived from CIM_ManagedSystemElement which forms the base for any element we want to provide in a WBEM schema.

Figure 1. Elements in the WMI CIM

As you can see, CIM_LogicalElement is the base class for many Win32_ schema classes like Win32_Registry, Win32_NetworkClient, Win32_Session, etc. CIM_PhysicalElement is the base class for physical devices; for example a Win32_DiskDrive is associated with a Win32_DiskDriverPhysicalMedia which is derived from CIM_PhysicalMedia. CIM_PhysicalComponent is derived from CIM_PhysicalElement and forms the base class for CIM_PhysicalMedia. Everything in the WMI CIMV2 can be found to be derived either from CIM_LogicalElement, CIM_PhysicalElement or even directly from CIM_ManagedSystemElement.

You can find a nice UML diagram of the Core Information Model at the DMTF website, and information about the committees in charge of creating the CIM here. Finally, you can download the CIM definition from the DMTF homepage in various formats: a whitepaper defining the objects, as Managed Object Format (.mof) files, Visio UML diagrams and XML files.

Currently, there are several industry implementations of the CIM: Microsoft WMI, Sun WBEM Services, WBEMSource (open source initiative) and Cisco CiscoWorks2000.

Navigating the CIMV2

To work effectively with CIMV2 you must first decide exactly what data you want to manage. Many problems can be solved by using just parts of the hierarchies rather than the entire model. You certainly won't need to break down every Win32_Service instance down to its CIM_ManagedSystemElement just to write a simple service manager. But you will need sufficient knowledge to find appropriate associations in the schema to get all the data you want.

Finding all the users of a group isn't easy without knowing the associations. Base classes depend on the cardinalities of a managed object.

An object may have more than one value for a given property when a class contains multi-valued properties such as a value array (for properties) or a one-to-many relation (for associations). This situation is sometimes referred to as the cardinality of the property or relationship. Identifying cardinality is important, because it's typically a fundamental part of the property or relationship definition. You express the cardinality of relationships using Max and Min qualifiers on the references and the cardinality of properties using array syntax and an ArrayType qualifier on the property.

For example a Win32_Process is not only associated with the Win32_ComputerSystem on which the process is running. It is also related with CIM_DataFile objects representing the files that are used by this process. In this case you'd have two cardinalities on the relation. See Code Sample 1 accompanying this article.

WMI Extensions Object Browser

Explaining a generic way to locate objects in any CIM implementation would be far beyond the scope of this article; however in the WMI implementation, the easiest way to find objects is to use the Windows Management Instrumentation Extensions for Microsoft Visual Studio .NET (see link section for download). After installing the extensions you'll see two new nodes in the Server Explorer panel: Management Classes and Management Events (see Figure 2).

Figure 2.

Figure 2. The screenshot shows the CIM filtered and sorted for the relevant objects in the Windows environment. Although this appears to be a complete list it is actually missing many objects because the WMI Extensions object browser shows only Management Classes that have instances.

For example, a Win32_PrintJob won't appear in this list because it is instantiated only while a document is being printed. Despite its limitations, the WMI Extensions browser provides a good place to start exploring the WMI model. You can get further details about any object by right-clicking on it and selecting the Properties menu item. Information relevant to the selected object appears in the properties window.

The Management Events Node is a tool that lest you test WMI Events inside VS.NET. You can register for a management event and see the results in the output pane of VS.NET. We will look further into events in a later article, explaining all the event stuff would be far beyond the scope of this article.

WMI Object Browser

A more powerful WMI object browser ships with the WMI SDK. Although it's more difficult to use, this version lets you browse through the currently running instances of CIM classes, and also provides much more detailed information than the WMI Extensions for VS.NET browser.

By default the Win32_ComputerSystem object is the root object; all other objects appear lower in the tree; their exact position depends on their association with the root object and with each other. If you want to zoom in on just a portion of the hierarchy you can right click on any object and make it the new tree root. The right pane gives you detailed information about properties, methods and associations of an object.

The methods and properties are self-explanatory, but associations aren't as transparent. Take a closer look at the associations tab which shows all the relevant information you will need to find associated objects. In this example you can see an instance of a Win32_BIOS class which is associated with the Win32_ComputerSystem class. The association is handled by an associator class, in this case Win32_SystemBIOS. Figure 3 shows how this class looks in the CIM Studio:

Figure 3. WMI Object Browser showing
Win32_SystemBIOS Management Class

The class shown in Figure 3 has two key properties: GroupComponent and PartComponent (see Figure 4).

Figure 4. Win32_SystemBIOS is the Associator of Win32_ComputerSystem and Win32_BIOS

The GroupComponent references the Win32_ComputerSystem instance containing the BIOS of the association and the PartComponent references Win32_BIOS contained in the computer system of this association.

How to Use CIMV2 in .NET Code

The first article in this series provided some basic knowledge about the .NET classes in the System.Management namespace. This article doesn't focus on introducing new classes, but you'll see how to use a function called GetRelated() to explore associations in the CIM.

Sample Application 1

This short program demonstrates nicely how powerful WMI is. Consider a simple question you've probably asked yourself many times: "What files does this process execute?" WMI gives you a simple way to answer the question. The class Win32_Process has an association class called CIM_ProcessExecutable which links to a CIM_DataFile class. You simply follow the relationships to find the answer.

First, create a new console application and add a reference to the System.Management.dll assembly.

using System;
using System.Management;

namespace ProcessFileListing
{
   class ProcessFileListing
   {
      static void Main(string[] args)
      {
         ManagementClass processes = new 
            ManagementClass("Win32_Process");

         foreach(ManagementObject process in  
            processes.GetInstances())
         {
                 Console.WriteLine(process["Caption"]);
            foreach(ManagementObject file in 
               process.GetRelated("CIM_DataFile"))
            {
               Console.WriteLine("\t{0}", 
                  file["Caption"]);
               Console.WriteLine();
            }
         }
      }
   }
}

The code first creates a ManagementClass object for the Win32_Process CIM class. Then it calls GetInstances() to acquire all the running processes as a ManagementObjectCollection which it traverses with a simple foreach loop to output the process name on the screen. Next, it calls the GetRelated() method on each process to obtain any relations of type CIM_DataFile. Calling GetRelated() without specifying the type of the related class would return all relations; in this case that would include the user logon sessions and the computer system on which the process is running. The call to GetRelated() returns another ManagementObjectCollection. The code loops through that with foreach outputting the file name on the screen. Figure 5 shows some sample output from the application.

Figure 5. Screenshot from the ProcessFileListing Application

Sample Application 2

The second sample application is a tiny WMI Browser. Users can enter the name of a WMI class to get all running instances of this class, and then select an instance to show items related to that instance. Note that the browser uses the instances to show related objects rather than related classes. While all instances of a class have the same related classes, separate instances of the same class often have different related objects, because they're related to different components of a system. For example, the CPU and the graphics adapter are both instances of a Win32_PnPEntity, but the graphic adapter is related to a monitor, while the CPU isn't. Figure 6 shows the mini-browser in action.

Figure 6. Screenshot of the Relationshipbrowser Sampleapplication

When you type in a class name and click the Get Instances button, the browser fills the combo box in the Instances section of the form with a list of instance paths. The code generates each instance path by concatenating the class name, a dot and a comma-separated list of key-value pairs with the key values of our instances. The key property will be the Name property on most objects, although there are several objects with different keys. For example, in Figure 5, the key property is the DeviceID. In contrast, a Win32_UserAccount (not shown) has two keys: Name and Domain. When you select an instance, the browser fills the Related Objects section of the form with information that's retrieved using the GetRelated() method.

Here's the code that fills the Instances combo box. You'll find some new WMI elements that you haven't seen before in this code:

ManagementClass mc = new ManagementClass(txtClassName.Text);

cbInstances.Items.Clear();

foreach (ManagementObject mo in mc.GetInstances())
{
   string s = mo.ClassPath.ClassName;
   int count=0;

   foreach(PropertyData pd in mo.Properties)
   {
      try
      {
         if ((bool)pd.Qualifiers["key"].Value)
         {
            if(count==0)
               s += ".";
            else
               s += ",";
            s+= pd.Name + "=";
            if(pd.Type==CimType.String)
               s += "'" + pd.Value + "'";
            else
               s += pd.Value;
            count ++;
         }
      }catch{}
   }
   cbInstances.Items.Add(s);
}

Author's Note: The error handling code is omitted in the code fragment above.

The preceding code first creates a ManagementClass instance (mc), passing the class name the user entered into the Class TextBox to the constructor. Next it clears the Instances ComboBox and iterates over all available instances of the mc ManagementClass, filling the ComboBox with the unique instance identification strings.

To find the key properties for each instance you iterate through all its properties, checking whether the property qualifier "key" is set to true. Non-key properties don't have this qualifier, so the code throws an exception when the qualifier doesn't exist. As you're not interested in non-key properties, you can simply ignore the exception in the catch part of the try/catch statement.

The code outputs a dot (.)to separate the instance name from the first key/value property pair, but uses a comma for all subsequent properties. It also adds surrounding single-quotes to the value when the key type is string.

Clicking the "Show Related" button causes the application to display detailed information about the selected item in the multi-line TextBox in the Related Objects section of the form. Listing 1 shows the code in the OnClick event handler of the button.

try
{
   txtRelated.Text = string.Empty;
   Cursor = Cursors.WaitCursor;

   ManagementObject mo = new ManagementObject(
      (string)cbInstances.SelectedItem.ToString());
   mo.Get();

   foreach(ManagementObject m in mo.GetRelated())
   {
      string s = m.ClassPath.ClassName + "\r\n";
      s += "\tSystem Properties:\r\n";
      foreach(PropertyData p in m.SystemProperties)
         s += String.Format("\t\t{0} : {1} ({2})\r\n", 
            p.Name, p.Value, p.Type);
      s += "\tProperties:\r\n";
      foreach(PropertyData p in m.Properties)
      {
         s += String.Format("\t\t{0} : {1} ({2})", 
            p.Name, p.Value, p.Type);
         if(p.IsArray)
            s += " [Array]";
         if(p.IsLocal)
            s += " [Local]";
         else
            s += " [from " + p.Origin + "]";
         s += "\r\n";
      }

   ManagementClass mc = new ManagementClass(
      m.ClassPath.ClassName);
   s += "\tMethods:\r\n";
   foreach(MethodData md in mc.Methods)
   {
      s += String.Format("\t\t{0} :\r\n", md.Name);
      if(md.InParameters!=null)
      {
         s += "\t\t\tIn Parameters:\r\n";                  
         foreach(PropertyData p in md.InParameters.Properties)
            s += String.Format("\t\t\t\t{0} : {1}\r\n", 
               p.Name, p.Type);
      }
      if(md.OutParameters!=null)
      {
         s += "\t\t\tOut Parameters:\r\n";                  
         foreach(PropertyData p in 
            md.OutParameters.Properties)
            s += String.Format("\t\t\t\t{0} : {1}\r\n", 
               p.Name, p.Type);
      }

      ManagementClass mc = new ManagementClass(
         m.ClassPath.ClassName);
      s += "\tMethods:\r\n";
      foreach(MethodData md in mc.Methods)
      {
         s += String.Format("\t\t{0} :\r\n", md.Name);
         if(md.InParameters!=null)
         {
            s += "\t\t\tIn Parameters:\r\n";                  
            foreach(PropertyData p in 
               md.InParameters.Properties)
               s += String.Format("\t\t\t\t{0} : {1}\r\n", 
                  p.Name, p.Type);
         }
         if(md.OutParameters!=null)
         {
            s += "\t\t\tOut Parameters:\r\n";                  
            foreach(PropertyData p in 
               md.OutParameters.Properties)
               s += String.Format("\t\t\t\t{0} : {1}\r\n", 
                  p.Name, p.Type);
         }
         s += "\r\n";
      }

      s += "\r\n";

      txtRelated.Text += s;
   }
}
catch(Exception ex)
{
   MessageBox.Show(ex.Message);
}
finally
{
   Cursor = Cursors.Default;
}

The code clears the txtRelated TextBox, displays an hourglass cursor, and then creates a new ManagementObject instance using the unique instance string from the item selected in the cbInstances ComboBox. Then it calls the Get() method to acquire the instance from the WMI repository. It then calls the GetRelated() Method and iterates over all the related objects found in the repository. The code doesn't test in advance for any particular type of object—you won't necessarily know what kind of objects the GetRelated() method will retrieve. That means it must process the retrieved objects generically.

Consider how to display the name of a retrieved object: it's simple, just add the value of the ClassPath property to the output. But we don't want the full ClassPath value, just the last part of it—the class name. You could split the string manually, but that's too much trouble, even using the convenient System.String functions. If you look carefully you'll see that ClassPath is of type ManagementPath, a neat helper class that stores each part (server, namespace, class name …) of the class path as individual members. Knowing that, you can get the object's name using the ClassPath.ClassName property.

The next step is to loop through all the system properties and add their names, values and types to the output. That's straightforward, and you can study Listing 1 to see how it's done. Retrieving the non-system properties is more interesting, because although the basic operation is the same, you need to check a few extra members.

For example, if the property is an array than the IsArray qualifier will return true, and you can iterate through the array members. Although this is a qualifier you can access it directly via a member of the PropertyData object. The PropertyData object represents a collection of properties of management classes. Be careful when iterating over properties with multiple values! If the IsLocal qualifier returns true then the property has been defined in this class instance; otherwise the property is inherited from some base class.

Displaying methods is a little harder. Because methods are not specific to a certain instance but are common to all instances of the same class, you can't find the method collection in the ManagementObject class. Instead, you must use the ManagementClass, which has a collection of MethodData objects called (appropriately enough) methods that contain all the necessary information about method names, parameters and calling conventions. The CIM defines the input and output parameters for a method as a ManagementObject though. You can find the parameter name and type in the properties collection of the InParameters (or respectively OutParameters) object. Don't forget that methods exist with neither input nor output parameters. The ManagementObject representing these missing parameters is null.

Limitations

Bear in mind that acquiring such detailed information from the CIM requires heavy interaction with the running operating system and can drastically affect the performance of your system. Don't ever try to run this in an inner loop of an application; you'll wait for ages for it to finish such WMI tasks. Always remember that WMI is an implementation for Web Based Enterprise Management which presupposes that you will use it only for management purposes and primarily on distributed managed systems.

If you find yourself tempted to use WMI functionality on a local system you'll generally find methods in the Platform SDK or somewhere else in the Win32 environment. that will better fit your needs. Although WMI may seem to be the simplest way to find information about your system, in many cases it's slower than using equivalent Win32 API calls. For example, it's much faster to list users and their associated groups using the ADSI SDK than using the WMI. While WMI is the most universally applicable solution to almost any given management problem, it isn't always the fastest solution.

Author Bio:

Peter Koen is working as an independent Consultant, Author and Programmer since 1998. When not writing he is giving Lectures on Knowledge Management at an private University in Austria, the FHW - Fachhochschule der Wirtschaft. He is certified as a MCP, MCAD, MCSDT, MCSD.NET, MCDBA and MCT in the world of Microsoft technologies, on the Networkside he has accomplished the Allied Telesyn Certifications for CAI, CASE and IAT. Peter also founded a Company in Austria / Europe for Consulting, Programming, Serverhousing and professional Networkservice. In the beginning of 2004 he founded the SQL Server Usergroup Austria. For his outstanding activity on the Microsoft .NET Usergroups in Germany and Austria he was awarded with the Microsoft MVP - Most Valuable Professional for Visual C# and for SQL Server. You can send him a mail to articles@kema.at.

Entropy/Farbrausch